package com.irdstudio.apicenter.gateway.web;

import java.math.BigDecimal;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import com.irdstudio.apicenter.gateway.comm.MaccsPacket;
import com.irdstudio.apicenter.gateway.core.MaccsConstant;
import com.irdstudio.apicenter.gateway.core.dm.AppBaseInfo;
import com.irdstudio.apicenter.gateway.core.dm.AppChannelAccess;
import com.irdstudio.apicenter.gateway.core.dm.AppChannelForward;
import com.irdstudio.apicenter.gateway.core.dm.AppTradeLog;
import com.irdstudio.apicenter.gateway.core.jdbc.MaccsBaseDAO;
import com.irdstudio.apicenter.gateway.core.log.MaccsLogExecutor;
import com.irdstudio.apicenter.gateway.core.token.AccessToken;
import com.irdstudio.apicenter.gateway.core.token.AccessTokenUtil;
import com.irdstudio.apicenter.gateway.security.TradeSecurityCheckUtil;
import com.irdstudio.apicenter.gateway.util.ServletUtil;
import com.irdstudio.apicenter.gateway.util.date.CurrentDateUtil;
import com.irdstudio.apicenter.gateway.util.pub.KeyUtil;
import com.irdstudio.apicenter.gateway.util.spring.BeanUtils;
import com.irdstudio.apicenter.gateway.web.controller.IController;
import com.irdstudio.apicenter.gateway.web.view.View;

/**
 * 通讯控制总线主程序
 * 
 * @author guangming.li
 * @version 1.0
 * @date 2015-05-20
 * @modify by ZhangHao
 * @date 2015-6-8
 *       <p>
 *       该类中使用的对象纳入Spring bean 管理
 *       <p>
 *       数据库操作统一采用{@link MaccsBaseDAO}
 *       <p>
 *       使用Spring 进行事务管理 @Transactional
 * 
 */
public class MaccsControllerBus {

	/* 移动应用基本信息 */
	private AppBaseInfo appBaseInfo = null;

	/* 通讯控制总线下的渠道接入配置（单个） */
	private AppChannelAccess channelAccess = null;

	/* 通讯控制总线下的渠道转发配置（可以有多个转发渠道） */
	private List<AppChannelForward> channelForwardList = null;

	/* 通讯控制总线下支持的交易列表 */
	private Map<String, MaccsTradeDefineVo> appTradeTable = null;

	/* 配置信息是否已经检查通过 */
	private boolean ready;

	/* 文本日志记录对象(Logback对象) */
	private final static Logger logger = LoggerFactory.getLogger(MaccsControllerBus.class);

	/**
	 * 构造函数
	 */
	public MaccsControllerBus(AppBaseInfo appBaseInfo) {
		this.ready = false;
		this.appTradeTable = new Hashtable<String, MaccsTradeDefineVo>();
		this.appBaseInfo = appBaseInfo;
	}

	/**
	 * 获取自定义的私有请求
	 * 
	 * @param tradeId
	 * @return
	 */
	public MaccsTradeDefineVo getMaccsTradeDefineVo(String tradeId) {
		return appTradeTable.get(tradeId);
	}

	/**
	 * 通讯控制总线交易处理入口
	 * 
	 * @param tradeId
	 * @param request
	 * @param response
	 */
	public void execute(String tradeId, HttpServletRequest request, HttpServletResponse response) {

		// 任何进入到通讯控制总线的交易请求必须记入数据库日志
		long startMills = System.currentTimeMillis();
		AppTradeLog calledLog = new AppTradeLog();
		calledLog.setAppId(this.getAppBaseInfo().getAppId());
		calledLog.setClientIp(ServletUtil.getRealClientIP(request));
		calledLog.setClientUserid("admin");
		calledLog.setStartTime(CurrentDateUtil.getTodayDateEx2());
		calledLog.setTradeCode(tradeId);
		calledLog.setTradeSerialNo(KeyUtil.createUniqueKey());

		// 捕获所有交易处理过程中的异常情况并以JSON返回给应用端
		try {
			// 获取需要执行的交易的定义信息,如果没找到，直接返回JSON错误信息(超出处理范围)
			MaccsTradeDefineVo tradeDefine = appTradeTable.get(tradeId);
			if (tradeDefine == null) {
				logger.error("Invalid request:" + tradeId);
				throw new MaccsException("该交易无效，不在通讯总线处理范围之内");
			}
			StringBuffer sb = new StringBuffer("Accepted request,appId:");
			sb.append(this.getAppBaseInfo().getAppId());
			sb.append(",tradeCode=").append(tradeId);
			sb.append(",tradeName=").append(tradeDefine.getTradeName());
			sb.append(",class=").append(tradeDefine.getTradeClass());
			logger.info(sb.toString());
			calledLog.setTradeName(tradeDefine.getTradeName());
			calledLog.setForwardChannelId(tradeDefine.getForwardChannelId());

			// 获得所有请求的参数集合
			MaccsPacket inPacket = ServletUtil.getAllRequestValues(request);
			logger.info("Accepted request,Parameter [" + inPacket.toString() + "]");
			calledLog.setInBuffer(inPacket.toString());

			// 获得请求头(Cookie)
			Cookie[] cookies = request.getCookies();
			logger.info("Accepted request, header:" + request.getHeader("Cookie"));
			logger.info("Accepted request, Cookie:" + cookies);
			// 获取客户端IP
			String clientIp = ServletUtil.getRealClientIP(request);
			// 交易安全检查--是否检查会话
			if (tradeDefine.isCheckToken() && isNotWeiXinIp(clientIp)) {

				String token_Id = inPacket.getValue("tokenId");
				// 从数据库中查询token信息
				AccessToken token = AccessTokenUtil.getAccessToken(token_Id);
				// 判断,如果有错,则抛出
				if (token.getLastTokenId() != null && token.getLastTokenId().length() > 0) {
					// 令牌失效，获取last_token_id,查询是新的token对象
					String last_token_id = token.getLastTokenId();
					// 查询新token对象
					AccessToken newToken = AccessTokenUtil.getAccessToken(last_token_id);
					throw new MaccsTokenException("9099", "你的账号已经在另一台设备上登录", newToken);
				}

				// 交易安全检查--是否检查设备一致性(登录时的设备与发起交易时的设备是否一致)
				if (tradeDefine.isCheckDevice()) {
					if (!TradeSecurityCheckUtil.isSameDevice(token, inPacket)) {
						throw new MaccsTokenException("9009", "当前发起交易的设备与登录时使用的设备不一致，请重新登录后再执行交易", token);
					}
				}

				// 交易安全检查--是否检查网络一致性(登录时的网络与发起交易时的网络是否一致)
				if (tradeDefine.isCheckNetwork()) {
					if (!TradeSecurityCheckUtil.isSameNetwork(token, clientIp)) {
						throw new MaccsTokenException("9010", "网络环境已经发生变化，请重新登录后执行", token);
					}
				}
			}

			IController tradeController = (IController) BeanUtils.getBean(Class.forName(tradeDefine.getTradeClass()));
			Assert.notNull(tradeController, "未找到交易码[" + tradeId + "]对应的处理类.");

			// 执行交易并记录交易结果到日志表中
			inPacket.addVariable("appId", appBaseInfo.getAppId());
			inPacket.addVariable("shareAppId", appBaseInfo.getShareAppId());
			View view = tradeController.execute(tradeDefine, inPacket, request, response);
			calledLog.setOutBuffer(view.getViewData());

			// 将Cookies写回到Response对象，返回给客户端
			cookies = request.getCookies();
			if (cookies != null) {
				for (Cookie c : cookies) {
					response.addCookie(c);
				}
				logger.info("Response cookies, values:" + cookies);
			}

			calledLog.setTradeResult(MaccsConstant.APP_TRADE_RESULT_SUCCESS);
			view.renderView();

		} catch (MaccsTokenException e) {
			e.printStackTrace();
			calledLog.setTradeResult(MaccsConstant.APP_TRADE_RESULT_FAILURE);
			MaccsRenderUtil.renderTokenErrorJsonView(e.getErrorCode(), e.getShowMessage(), e.getAccessToken(),
					response);
		} catch (MaccsException e) {
			e.printStackTrace();
			calledLog.setTradeResult(MaccsConstant.APP_TRADE_RESULT_FAILURE);
			MaccsRenderUtil.renderCustomErrorJsonView(e.getErrorCode(), e.getMessage(), response);
		} catch (Exception e) {
			e.printStackTrace();
			calledLog.setTradeResult(MaccsConstant.APP_TRADE_RESULT_FAILURE);
			MaccsRenderUtil.renderCustomErrorJsonView(Integer.toString(MaccsRenderUtil.CUSTOM_EXCEPTION),
					" " + e.getMessage(), response);
		} finally {

			// 登记交易结束时间并计算交易耗时（秒）
			long endMills = System.currentTimeMillis();
			calledLog.setEndTime(CurrentDateUtil.getTodayDateEx2());
			calledLog.setCostTime(new BigDecimal(endMills - startMills).divide(new BigDecimal(1000.00)));

			// 无论成功还是失败，写入日志进入数据库
			MaccsLogExecutor.writeTradeCalledLog(calledLog);
		}

	}

	private boolean isNotWeiXinIp(String clientIp) {
		if ("20.65.25.240".equals(clientIp)) {
			return false;
		}
		return true;
	}

	public void setChannelAccess(AppChannelAccess channelAccess) {
		this.channelAccess = channelAccess;
	}

	public AppChannelAccess getChannelAccess() {
		return channelAccess;
	}

	public void setReady(boolean ready) {
		this.ready = ready;
	}

	public boolean isReady() {
		return ready;
	}

	public void setChannelForwardList(List<AppChannelForward> channelForwardList) {
		this.channelForwardList = channelForwardList;
	}

	public List<AppChannelForward> getChannelForwardList() {
		return channelForwardList;
	}

	/**
	 * 增加可处理的交易定义信息
	 * 
	 * @param tradeId
	 * @param mtdv
	 */
	public void addMaccsTradeDefine(String tradeId, MaccsTradeDefineVo mtdv) {
		appTradeTable.put(tradeId, mtdv);
	}

	public AppBaseInfo getAppBaseInfo() {
		return appBaseInfo;
	}

	public void setAppBaseInfo(AppBaseInfo appBaseInfo) {
		this.appBaseInfo = appBaseInfo;
	}

}
